#include <map>
#include <vector>
#include <iostream>

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include <fcntl.h>
#include <assert.h>
#include <unistd.h>
#include <termios.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/select.h>

#include "tty_io.hpp"
#include "helper.hpp"

int gsm_fd = -1;

extern std::map<std::string, Serial_info> port_map;

b_packet_t* send_request(const std::string port, b_packet_t* req, 
        struct timeval read_timeout)
{	
    char *ptr;
    fd_set readfds;
    int c, s, rv, sum = 0;
    b_packet_t* resp = NULL;
    int fd = port_map[port].s_fd;
    bool skip_garbage = true;

    //lock the corresponding mutex for I/O. 
    ///////////////////////////////////////////////////////////////////////////////////////////////
    //                              SIMULATION
    ///////////////////////////////////////////////////////////////////////////////////////////////
    //{
        //pthread_mutex_lock(&port_map[port].s_mutex);
        //resp = (b_packet_t*)malloc(sizeof(b_packet_t));
        //resp->size = 20;
        //resp = (b_packet_t*)realloc(resp, sizeof(b_packet_t) + resp->size);
        //memset(resp, 0, sizeof(b_packet_t) + resp->size);
        //resp->size = 20;
        //pthread_mutex_unlock(&port_map[port].s_mutex);
        //return resp;
    //}
    ///////////////////////////////////////////////////////////////////////////////////////////////

    /* lock the corresponding mutex for I/O. */
    pthread_mutex_lock(&port_map[port].s_mutex);

    s = sizeof(b_packet_t) + req->size;
    c = write(fd, req, s);
    if(c != s){
        logger(SM_LOG_ERR,"ERROR: unable to send req [%s], port = %s\n", 
                pkt_to_str(req).c_str(), port.c_str());
        resp = NULL;
        goto unlock;
    }

    resp = (b_packet_t*)malloc(sizeof(b_packet_t));
    assert(resp != NULL);

    c = 0;
    ptr = (char*)resp;

    while(true) {
        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);
        struct timeval timeout = read_timeout;
        rv = select(fd + 1, &readfds, NULL, NULL, &timeout);

        if(rv <= 0) {
            logger(SM_LOG_ERR,"ERROR: failed to read between specified timeout [%s], port = %s, rv = %d\n", 
                    pkt_to_str(req).c_str(), port.c_str(), rv);
            free(resp); resp = NULL;
            goto unlock;
        }

        read(fd, ptr, 1);
        if(skip_garbage && *ptr != 58) continue;
        else skip_garbage = false;

        if(ptr == (char*)&resp->size) {
            resp = (b_packet_t*)realloc(resp, sizeof(b_packet_t) + resp->size);
            assert(resp != NULL);
            ptr = (char*)&resp->size;
            c = resp->size;
        }

        if(ptr >= (char*)&resp->size && c == 0) 
            break;

        ptr++; c--; // TODO: c can be minus.
    }

    /* checksum. */ //TODO
    if(resp->size > 0) {
        ptr = (char*)resp;
        int size = resp->size + sizeof(b_packet_t);
        for(int i = 0; i < size; ++i) {
            sum += *ptr++;
        }
    }

    if(resp->size == 0 || sum % 256 != 0) {
        logger(SM_LOG_ERR,"ERROR: checksum is not correct [%s]\n",
                pkt_to_str(resp).c_str());
        free(resp); resp = NULL;
        goto unlock;
    }

    if(resp->data[0] >= 200 && resp->data[0] <= 250) {
        logger(SM_LOG_ERR,"ERROR reported from panel/device: %s\n", pkt_to_str(resp).c_str());
        free(resp); resp = NULL;
        goto unlock;
    }

unlock:
    pthread_mutex_unlock(&port_map[port].s_mutex);
    return resp;
}

int init_tty(const std::string port, speed_t baud_rate)
{
    struct termios termio;
    int fd, r;

    memset (&termio, 0, sizeof (termio));

    if ((fd = open (port.c_str(), O_RDWR | O_NDELAY | O_NOCTTY)) == -1) {
        logger(SM_LOG_ERR,"Unable to open the port %s\n", port.c_str());
        exit(-1);
    }

    fcntl(fd, F_SETFL, 0);
    r = tcgetattr(fd, &termio);

    /* set the baud rate to 38400. */
    cfsetspeed(&termio, baud_rate);

    /* set parity to none. */
    termio.c_cflag &= ~(PARENB | PARODD);

    /* set databits to 8. */
    termio.c_cflag = ((termio.c_cflag & ~CSIZE) | CS8);

    /* set flow control to none. */
    termio.c_cflag &= ~(CRTSCTS);
    termio.c_iflag &= ~(IXON | IXOFF | IXANY);
    termio.c_cflag |= (CLOCAL | CREAD);

    /* apply our port config. */
    r = tcsetattr(fd, TCSANOW, &termio);

    if (r == -1) {
        logger(SM_LOG_ERR,"Unable to set attributes: %s\n", port.c_str());
        exit(-1);
    }

    return fd;
}

void init_gsm()
{
    struct termios options;

    std::map<std::string, Serial_info>::iterator p_it;
    p_it = port_map.find("/dev/gsm");
    if(p_it != port_map.end()){
        gsm_fd = port_map["/dev/gsm"].s_fd;
    } else {
        logger(SM_LOG_INFO,"SKIPPING GSM DEVICE INITIALIZATION\n");
        return;
    }

    // Get the current options for the port...
    tcgetattr(gsm_fd, &options);

    // Set the baud rates to whatever is needed... (Max 115200)
    cfsetispeed(&options, B115200);

    // Enable the receiver and set local mode...
    options.c_cflag |= (CLOCAL | CREAD);

    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;

    // Set the new options for the port...
    tcsetattr(gsm_fd, TCSANOW, &options);

    for(int i = 0; i < 4; ++i) {
        char tmp[4];
        char buff[64];
        tmp[0] = 'A';
        tmp[1] = 'T';
        tmp[2] = 0;
        logger(SM_LOG_INFO,"Initializing GSM ...\n");
        write_to_gsm(tmp);
        read_from_gsm(buff);
        logger(SM_LOG_INFO,"OK\n");
    }
}
